#include <net/net.h>
#include <g-bios.h>
#include <sysconf.h>
#include <flash/flash.h>

#define DEF_GB_SIZE              KB(512)
#define DEF_LINUX_IMAGE_SIZE    (MB(3) - DEF_GB_SIZE)
#define DEF_RAMDISK_IMAGE_SIZE   MB(6)

static struct ImageCache gKernelCache;
static struct ImageCache gRamdiskCache;

static struct SysConf gSysConf;

// static int g_nVol = 0;


// fxime:
static const struct PartAttr g_vFlashParts[] =
{
	{
		.nPartType  = PT_BL_GBIOS,
		.ulPartSize = DEF_GB_SIZE,
		.szPartName = "g-bios"
	},
	{
		.nPartType  = PT_OS_LINUX,
		.ulPartSize = DEF_LINUX_IMAGE_SIZE,
	},
#if 1
	{
		.nPartType	= PT_FS_YAFFS2,
		.ulPartSize = MB(24),
		.szPartName = "rootfs"
	},
	{
		.nPartType	= PT_FS_YAFFS2,
		.szPartName = "data"
	},
#else
	{
		.nPartType  = PT_FS_RAMDISK,
		.ulPartSize = DEF_RAMDISK_IMAGE_SIZE,
	},
	{
		.nPartType	= PT_OS_WINCE,
		.ulPartSize = MB(92),
	},
	{
		.nPartType  = PT_FS_YAFFS2,
		.ulPartSize = MB(64),
	},
	{
		.nPartType	= PT_FS_YAFFS2,
		.ulPartSize = MB(64),
	},
	{
		.nPartType	= PT_FS_YAFFS2,
	},
#endif
};


static const BYTE g_vbDefMac[MAC_ADDR_LEN] = CONF_MAC_ADDR;

struct ImageCache *GuImageCacheGet(PART_TYPE enType)
{
	switch (enType)
	{
	case PT_OS_LINUX:
		return &gKernelCache;

	case PT_FS_RAMDISK:
		return &gRamdiskCache;
	}

	return NULL;
}


// fixme: to fix cross/overlapped parts
static long AdjustPartTab(struct Flash *pHost,
						struct PartAttr *pNewPartAttr,
						const struct PartAttr *pPartAttrTmpl,
						UINT32 nParts
						)
{
	long  nIndex;
	ULONG ulCurrStart;


	if (nParts > MAX_FLASH_PARTS)
	{
		nParts = MAX_FLASH_PARTS;
	}

	ulCurrStart  = pPartAttrTmpl[0].ulPartBase;
	ulCurrStart -= ulCurrStart % pHost->nEraseSize; // fixme: to use macro

	for (nIndex = 0; nIndex < nParts; nIndex++)
	{	
		if (ulCurrStart + pHost->nEraseSize > pHost->nChipSize)
			break;

		if (0 == pPartAttrTmpl->ulPartBase)
		{
			pNewPartAttr->ulPartBase = ulCurrStart;
		}
		else
		{
			pNewPartAttr->ulPartBase = pPartAttrTmpl->ulPartBase;

			ALIGN_UP(pNewPartAttr->ulPartBase, pHost->nEraseSize);

			ulCurrStart = pNewPartAttr->ulPartBase;
		}

		if (0 == pPartAttrTmpl->ulPartSize)
		{
			pNewPartAttr->ulPartSize = pHost->nChipSize - ulCurrStart;
		}
		else
		{
			pNewPartAttr->ulPartSize = pPartAttrTmpl->ulPartSize;

			ALIGN_UP(pNewPartAttr->ulPartSize, pHost->nEraseSize);
		}

		if (pNewPartAttr->ulPartBase + pNewPartAttr->ulPartSize > pHost->nChipSize)
		{
			break;
		}

		ulCurrStart += pNewPartAttr->ulPartSize;

		pNewPartAttr->nPartType = pPartAttrTmpl->nPartType;

		if ('\0' == pPartAttrTmpl->szPartName[0])
			strncpy(pNewPartAttr->szPartName, GuPartTypeToStr(pNewPartAttr->nPartType), sizeof(pNewPartAttr->szPartName));
		else
			strncpy(pNewPartAttr->szPartName, pPartAttrTmpl->szPartName, sizeof(pNewPartAttr->szPartName));

		pNewPartAttr++;
		pPartAttrTmpl++;
	}

#if 1
	if (ulCurrStart + pHost->nEraseSize <= pHost->nChipSize)
	{
		pNewPartAttr->nPartType   = PT_FREE;
		pNewPartAttr->ulPartBase = ulCurrStart;
		pNewPartAttr->ulPartSize  = pHost->nChipSize - ulCurrStart;

		strcpy(pNewPartAttr->szPartName, "free");
		// nPartNum++;
		nIndex++;

		// g_nVol++;
	}
#endif

	return nIndex;
}


static UINT32 SysConfCheckSum(UINT32 *pNewSum)
{
	UINT32 dwOldSum = gSysConf.dwCheckSum;

	gSysConf.dwCheckSum = 0;
	gSysConf.dwCheckSum = ~ GuNetCheckSum(&gSysConf, sizeof(gSysConf)) << 16;
	gSysConf.dwCheckSum |=GB_SYSCFG_VER;

	if (NULL != pNewSum)
		*pNewSum = gSysConf.dwCheckSum;
 
	return dwOldSum;
}


static __INIT__ int GuSysConfSetDefault(struct SysConf **ppSysConf)
{
	int i;
	struct Flash *pFlash;
	struct PartInfo *pPartInfo  = &gSysConf.mPartInfo;
 	struct NetConf *pNetConf    = &gSysConf.mNetConf;
	struct LinuxParam *pKeParam = &gSysConf.mLinuxParam;


	memset(&gSysConf, 0x0, sizeof(gSysConf)); // to be removed

	pFlash = GuFlashOpen(BOOT_FLASH_ID);

	pPartInfo->nParts =	AdjustPartTab(pFlash,
							pPartInfo->vAttrTab,
							g_vFlashParts,
							ARRAY_ELEM_NUM(g_vFlashParts)
							);

	pNetConf->dwLocalIP = CONF_LOCAL_IP;
	pNetConf->dwNetMask = CONF_NET_MASK;

	pNetConf->dwTftpSvrIP = CONF_SVR_IP;

	memcpy(pNetConf->vbLocalMac, g_vbDefMac, MAC_ADDR_LEN);
	GuNetSetMac(NULL, pNetConf->vbLocalMac);

	// fixme!!!
	pKeParam->szImgKernel[0] = '\0';

	pKeParam->szImgRd[0] = '\0'; // to be removed

	pKeParam->dwBootMode = BM_FLASHDISK;

	pKeParam->nRootDev = 0;

	for (i = 0; i < pPartInfo->nParts; i++)
	{
		struct PartAttr *pAttr = pPartInfo->vAttrTab + i;

		if (pAttr->nPartType > PT_FS_BEGIN && pAttr->nPartType < PT_FS_END)
		{
			pKeParam->nRootDev = i;
			break;
		}
	}

	GuIpToStr(pKeParam->szNfsSvr, CONF_SVR_IP);
	strncpy(pKeParam->szNfsPath, CONF_NFS_ROOT, NFS_PATH_LEN - 1);

	strncpy(pKeParam->szConDev, CONF_CONSOLE_NAME, CON_DEV_NAME_LEN);

	SysConfCheckSum(NULL);

	if (NULL != ppSysConf)
		*ppSysConf = &gSysConf;

	//fixme
	return 0;
}



int GuSysConfLoad(struct SysConf **ppSysConf)
{
	int ret = 0;
	ULONG nConfSize, nConfStart;
	UINT32 dwOldCheckSum, dwNewCheckSum;
	BYTE *pConfBuf;
	struct Flash     *pFlash;
	struct PartInfo  *pPartInfo;
	static struct SysConf *pSysConf = NULL;


	if (GB_SYSCFG_VER == (gSysConf.dwCheckSum & 0xff))
	{
		goto L2;
	}

	pFlash = GuFlashOpen(BOOT_FLASH_ID);

	if (NULL == pFlash)
	{
		return -ENODEV;
	}

	nConfStart = FLASH_CONF_ADDR;
	nConfSize  = pFlash->nEraseSize;
	// pFlash->nEraseSize > sizeof(struct SysConf) ? pFlash->nEraseSize : sizeof(struct SysConf);

	pConfBuf = (BYTE *)malloc(nConfSize);
	if (NULL == pConfBuf)
	{
		ret = -ENOMEM;
		goto L1;
	}

	ret = GuFlashRead(pFlash, pConfBuf, (int *)&nConfStart, nConfSize);
	if (ret < 0)
	{
		goto L1;
	}

	memcpy(&gSysConf, pConfBuf, sizeof(struct SysConf));

	free(pConfBuf);

	dwOldCheckSum = SysConfCheckSum(&dwNewCheckSum);

	if (dwOldCheckSum != dwNewCheckSum)
	{
		printf("checksum error! current = 0x%08x, original = 0x%08x.\n",
			dwNewCheckSum, dwOldCheckSum);

		ret = -EIO;
	}

L1:
	GuFlashClose(pFlash);

L2:
	if (NULL != ppSysConf)
		*ppSysConf = &gSysConf;

	return ret;
}


int GuSysGetPartInfo(struct PartInfo **ppPartInfo)
{
	*ppPartInfo = &gSysConf.mPartInfo;

	return 0;
}


int GuSysGetNetConf(struct NetConf **ppNetConf)
{
	*ppNetConf = &gSysConf.mNetConf;

	return 0;
}


int GuSysGetLinuxParam(struct LinuxParam **ppLinuxParam)
{
	*ppLinuxParam = &gSysConf.mLinuxParam;

	return 0;
}


int GuSysConfStore(void)
{
	int iRet;
	ULONG nConfBase, nConfSize;
	BYTE *pConfBuf;
    struct Flash *pFlash;
	struct SysConf *pSysConf = &gSysConf;


	pFlash = GuFlashOpen(BOOT_FLASH_ID);
	if (NULL == pFlash)
		return -ENODEV;

	nConfBase = FLASH_CONF_ADDR;
	nConfSize = pFlash->nEraseSize; // fixme!

	iRet = GuFlashErase(pFlash,
					nConfBase,
					nConfSize,
					EDF_ALLOWBB
					);

	if (iRet < 0)
	{
		goto L1;
	}

	SysConfCheckSum(NULL);

    pConfBuf = (BYTE *)malloc(nConfSize);
    if (NULL == pConfBuf) 
	{
		iRet = -ENOMEM;
		goto L1;
	}

    memcpy(pConfBuf, pSysConf, sizeof(struct SysConf));

	iRet = GuFlashWrite(pFlash, pConfBuf, nConfSize, &nConfBase);

    free(pConfBuf);

L1:
	GuFlashClose(pFlash);

	return iRet;
}


int GuSysConfGetRoot(void)
{
	return gSysConf.mLinuxParam.nRootDev;
}


static __INIT__ int PartInfoInit(struct Flash *pFlash, struct PartInfo *pPartInfo)
{
	struct Partition *pPart;
	struct PartAttr  *pAttr;


	pFlash->pPartInfo = pPartInfo;

	pPart = pFlash->vPartTab;
	pAttr = pPartInfo->vAttrTab;


	while (pPart < pFlash->vPartTab + MAX_FLASH_PARTS)
	{
		pPart->pAttr = pAttr;
		pPart->pHost = pFlash;

		pPart->pImgCache = NULL;

		switch (pAttr->nPartType)
		{
		case PT_OS_LINUX:
			pAttr->ulBuffSize = pFlash->nEraseSize;
			pPart->pImgCache = &gKernelCache;
			pPart->pImgCache->pCacheBase = malloc(pAttr->ulPartSize);
			if (NULL == pPart->pImgCache->pCacheBase)
			{
				printf("partition size of \"%s\" seems too large! (0x%08x)\n",
					GuPartTypeToStr(pAttr->nPartType), pAttr->ulPartSize);
			}
			pPart->pImgCache->ulCacheLen = 0;
			break;

		case PT_FS_RAMDISK:
			pPart->pImgCache = &gRamdiskCache;
			pPart->pImgCache->pCacheBase = malloc(pAttr->ulPartSize);
			if (NULL == pPart->pImgCache->pCacheBase)
			{
				printf("partition size of \"%s\" seems too large! (0x%08x)\n",
					GuPartTypeToStr(pAttr->nPartType), pAttr->ulPartSize);
			}
			pPart->pImgCache->ulCacheLen = 0;
		case PT_FS_CRAMFS:
		case PT_FS_JFFS2:
			pAttr->ulBuffSize = pFlash->nEraseSize;
			break;

		case PT_FS_YAFFS:
		case PT_FS_YAFFS2:		
			pAttr->ulBuffSize = (pFlash->nWriteSize + pFlash->nOobSize) << (pFlash->nEraseShift - pFlash->nWriteShift);
			break;

		default:
			break;
		}

		pPart++;
		pAttr++;
	}

	return 0;
}

static __INIT__ int SysConfInit(void)
{
	int ret;
	struct Flash      *pFlash;
	struct Partition  *pPart;
	struct SysConf    *pSysConf;
	struct NetConf    *pNetConf;
	struct LinuxParam *pLinuxParam;


	APP_INFO("System Configuration.\n");

	ret = GuSysConfLoad(&pSysConf);
	if (ret < 0)
	{
		printf("Fail to load sysconfig (errno = %d), Set to default!\n", ret);

		GuSysConfSetDefault(&pSysConf);

		if (ret != -ENODEV)
		{
			ret = GuSysConfStore();
			if (ret < 0)
			{
				printf("fail to store system configuration! (errno = %d)\n", ret);
			}
		}
	}

	pNetConf    = &pSysConf->mNetConf;
	pLinuxParam = &pSysConf->mLinuxParam;

	pFlash = GuFlashOpen(BOOT_FLASH_ID);
	// BUG_IF(pFlash == NULL);

	PartInfoInit(pFlash, &pSysConf->mPartInfo);

	if (NULL == gKernelCache.pCacheBase)
	{
		gKernelCache.pCacheBase = malloc(DEF_LINUX_IMAGE_SIZE);
	}

	if (NULL == gRamdiskCache.pCacheBase)
	{
		gRamdiskCache.pCacheBase = malloc(DEF_RAMDISK_IMAGE_SIZE);
	}

	GuPartShow(pFlash);

	GuFlashClose(pFlash);

	ret = GuNetSetMac(NULL, pNetConf->vbLocalMac);
	if (ret < 0)
	{
		return -ENAVAIL;
	}

	return 0;
}

APP_INIT(SysConfInit);


// fixme
static int main(int argc, char *argv[])
{
	GuSysConfSetDefault(NULL);
	GuSysConfStore();

	return 0;
}

INSTALL_APPLICATION(confreset, main, "reset sysconf.");

